/*
 * $Id: crule.c 111 2008-08-18 16:09:35Z dtikhonov $
 *
 * crule.c -- coloring rules implementation.
 */

#include <assert.h>
#include <errno.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
#include <tre/regex.h>

#include "crule.h"

typedef struct {
    int start;
    int sz;
    int fg;
    int bg;
    int pairno;
} crule_color_t;

typedef struct {
    crule_type_t    type;

    int             level;

    union {
        uint64_t    position;
        regex_t     preg;
        struct {
            uint8_t *str;
            int      sz;
        } exact;
    } u;

    crule_color_t *colors;
    int ncolors;

    struct {
        bool        regex;
    } set;
} crule_ctx_t;

static crule_type_t
_crule_type (crule_t *self)
{
    crule_ctx_t *ctx = self->ctx;
    return ctx->type;
}

static int
_crule_level (crule_t *self)
{
    crule_ctx_t *ctx = self->ctx;
    return ctx->level;
}

static void
_crule_set_level (crule_t *self, int level)
{
    crule_ctx_t *ctx = self->ctx;
    ctx->level = level;
    return;
}

static void
_crule_set_position (crule_t *self, uint64_t pos)
{
    crule_ctx_t *ctx = self->ctx;
    switch (ctx->type) {
        case CRULE_TYPE_ABSOLUTE:
        case CRULE_TYPE_PERIODIC:
            ctx->u.position = pos;
            break;

        case CRULE_TYPE_EXACT:
        case CRULE_TYPE_REGEX:
        default:
            assert(!"`set_position' should only be called on types "
                    "CRULE_TYPE_ABSOLUTE or CRULE_TYPE_PERIODIC:");
            break;
    }

    return;
}

static void
_crule_set_exact_string (crule_t *self, const uint8_t *buf, int sz)
{
    crule_ctx_t *ctx = self->ctx;
    if (CRULE_TYPE_EXACT != ctx->type) {
        assert(!"`set_exact_string' should only be called on type "
                "CRULE_TYPE_EXACT");
        return;
    }

    assert(NULL == ctx->u.exact.str);  // allow only once

    ctx->u.exact.str = malloc(sz);
    memcpy(ctx->u.exact.str, buf, sz);
    ctx->u.exact.sz = sz;

    return;
}

static bool
_crule_set_regex (crule_t *self, const char *_regex, int len, int flags)
{
    crule_ctx_t *ctx = self->ctx;
    if (CRULE_TYPE_REGEX != ctx->type) {
        assert(!"`set_regex' should only be called on type CRULE_TYPE_REGEX");
        return false;
    }

    /* Anchor at the beginning */
    char *regex = malloc(4 + len);
    regex[0] = '^';
    regex[1] = '(';
    memcpy(regex + 2, _regex, len);
    regex[len + 2] = ')';
    regex[len + 3] = 0;
    len += 3;

    int code;
    char err[0x100];
    if (0 != (code = regncomp(&ctx->u.preg, regex, len, flags|REG_EXTENDED)))
    {
        regerror(code, &ctx->u.preg, err, sizeof(err));
        printf("Can't compile regex: error %d: %s\n", code, err);
        free(regex);
        return false;
    }

    free(regex);
    ctx->set.regex = true;
    return true;
}

static int
_crule_match_absolute (crule_t *self, uint64_t current_position,
                       int off, const unsigned char *buf, int sz)
{
    crule_ctx_t *ctx = self->ctx;
    return (ctx->u.position == current_position + off ? 1 : 0);
}

static int
_crule_match_periodic (crule_t *self, uint64_t current_position,
                       int off, const unsigned char *buf, int sz)
{
    crule_ctx_t *ctx = self->ctx;
    return ((current_position + off) % ctx->u.position == 0 ? 1 : 0);
}

static int
_crule_match_exact (crule_t *self, uint64_t current_position,
                    int off, const unsigned char *buf, int sz)
{
    crule_ctx_t *ctx = self->ctx;

    if (ctx->u.exact.sz > sz - off)
        return false;

    return (0 == memcmp(ctx->u.exact.str, buf + off, ctx->u.exact.sz) ?
            ctx->u.exact.sz : 0);
}

static int
_crule_match_regex (crule_t *self, uint64_t current_position,
                    int off, const unsigned char *buf, int sz)
{
    crule_ctx_t *ctx = self->ctx;

    regmatch_t pmatch[1];

    if (0 == regnexec(&ctx->u.preg, (const char *) buf + off,
                      sz - off, 1, pmatch, 0))
    {
        return pmatch[0].rm_eo - pmatch[0].rm_so;
    } else {
        return 0;
    }
}

typedef int (*crule_match_func_t)(crule_t *self, uint64_t current_position,
                                   int off, const unsigned char *buf, int sz);
static struct {
    crule_match_func_t match;
} match_functions[] = { /* Indexed by crule_type_t */
    { NULL                      , },
    { _crule_match_absolute     , },
    { _crule_match_periodic     , },
    { _crule_match_exact        , },
    { _crule_match_regex        , },
};

#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
static void
_crule_apply_color_range (crule_t *self, const unsigned char *buf,
                          int bufsz, int offset, int *color_buffer,
                          int matchsz)
{
    assert(offset >= 0);
    assert(offset < bufsz);

    crule_ctx_t *ctx = self->ctx;

    int i;
    for (i = 0; i < ctx->ncolors; ++i) {
        assert(ctx->colors[i].pairno > 0);  // it better be set

        int start = MAX(offset + ctx->colors[i].start, 0);
        int sz    = ctx->colors[i].sz;

        /* If sz is zero, use the size of the match. */
        if (0 == sz)
            sz = matchsz;

        sz = MIN(sz, bufsz - offset);

        int j;
        for (j = 0; j < sz; ++j)
            color_buffer[start + j] = COLOR_PAIR(ctx->colors[i].pairno);
    }

    return;
}

static int
_crule_colorize (
    crule_t *self
  , uint64_t current_position
  , const unsigned char *buf
  , int sz
  , int *color_buffer
) {
    crule_ctx_t *ctx = self->ctx;
    int retval = 0;
    int i;
    crule_match_func_t match = match_functions[ctx->type].match;

    for (i = 0; i < sz; ++i) {
        int matchsz = match(self, current_position, i, buf, sz);

        if (0 == matchsz)
            continue;

        _crule_apply_color_range(self, buf, sz, i, color_buffer, matchsz);
        ++retval;
    }

    return retval;
}

static bool
_crule_is_initialized (crule_t *self)
{
    crule_ctx_t *ctx = self->ctx;

    if (0 == ctx->ncolors)
        return false;

    switch (ctx->type) {
        case CRULE_TYPE_ABSOLUTE:
        case CRULE_TYPE_PERIODIC:
            return ctx->u.position;

        case CRULE_TYPE_REGEX:
            return ctx->set.regex;

        case CRULE_TYPE_EXACT:
            return ctx->u.exact.sz;

        default:
            assert(!"invalid type");
            return false;
    }
}

static bool
_crule_add_color (crule_t *self, int start, int sz, int fg, int bg)
{
    crule_ctx_t *ctx = self->ctx;

    if (!(fg >= 0 && fg <= 7)) {
        assert(!"Foreground color must be between 0 and 7");
        errno = EINVAL;
        return false;
    }

    if (!(bg >= -1 && bg <= 7)) {
        assert(!"Background color must be between -1 and 7");
        errno = EINVAL;
        return false;
    }

    ctx->ncolors++;
    ctx->colors = realloc(ctx->colors, ctx->ncolors * sizeof(crule_color_t));
    ctx->colors[ctx->ncolors - 1].start = start;
    ctx->colors[ctx->ncolors - 1].sz    = sz;
    ctx->colors[ctx->ncolors - 1].fg    = fg;
    ctx->colors[ctx->ncolors - 1].bg    = bg;

    return true;
}

static void
_crule_init_color_pairs (crule_t *self, color_pairs_t *color_pairs)
{
    crule_ctx_t *ctx = self->ctx;

    int i;
    for (i = 0; i < ctx->ncolors; ++i) {
        int fg = ctx->colors[i].fg;
        int bg = ctx->colors[i].bg;

        if (-1 == bg)
            bg = COLOR_BLACK;   // TODO: grab real default color

        if (color_pairs->matrix[fg][bg]) {
            ctx->colors[i].pairno = color_pairs->matrix[fg][bg];
        } else {
            ++color_pairs->npairs;
            assert(color_pairs->npairs < 256);
            init_pair(color_pairs->npairs, fg, bg);
            ctx->colors[i].pairno =
            color_pairs->matrix[fg][bg] =
                color_pairs->npairs;
        }
    }

    return;
}

static void
_crule_destroy (crule_t *self)
{
    crule_ctx_t *ctx = self->ctx;

    if (ctx->colors)
        free(ctx->colors);

    switch (ctx->type) {
        case CRULE_TYPE_REGEX:
            if (ctx->set.regex)
                regfree(&ctx->u.preg);
            break;

        case CRULE_TYPE_EXACT:
            if (ctx->u.exact.str)
                free(ctx->u.exact.str);
        break;

        default:
            /* Nothing to do */
            break;
    }

    free(ctx);
    free(self);
    return;
}

crule_t *
crule_new (crule_type_t type)
{
    crule_ctx_t *ctx = calloc(1, sizeof(*ctx));
    ctx->type = type;

    crule_t *crule = calloc(1, sizeof(*crule));
    crule->ctx = ctx;

    /* Set up methods: */
    crule->type             = _crule_type;
    crule->level            = _crule_level;
    crule->set_level        = _crule_set_level;
    crule->set_position     = _crule_set_position;
    crule->colorize         = _crule_colorize;
    crule->is_initialized   = _crule_is_initialized;
    crule->add_color        = _crule_add_color;
    crule->set_exact_string = _crule_set_exact_string;
    crule->set_regex        = _crule_set_regex;
    crule->init_color_pairs = _crule_init_color_pairs;
    crule->destroy          = _crule_destroy;

    return crule;
}

typedef struct {
    int       nrules;
    crule_t **rules;
} crulist_ctx_t;

static bool
_crulist_add (crulist_t *self, crule_t *rule)
{
    if (!rule->is_initialized(rule))
        return false;

    crulist_ctx_t *ctx = self->ctx;

    ctx->rules = realloc(ctx->rules, (ctx->nrules + 1) * sizeof(crule_t *));
    ctx->rules[ctx->nrules] = rule;
    ++ctx->nrules;
    return true;
}

static int
_crulist_nrules (crulist_t *self)
{
    crulist_ctx_t *ctx = self->ctx;
    return ctx->nrules;
}

static int
_crulist_colorize (
    struct crulist *self
  , uint64_t current_position
  , const unsigned char *buf
  , int sz
  , int *color_buffer
) {
    crulist_ctx_t *ctx = self->ctx;
    int retval = 0;
    int i;

    for (i = 0; i < ctx->nrules; ++i)
        retval += ctx->rules[i]->colorize(ctx->rules[i], current_position,
                                          buf, sz, color_buffer);

    return retval;
}

static void
_crulist_init_colors (crulist_t *self, color_pairs_t *color_pairs)
{
    crulist_ctx_t *ctx = self->ctx;
    int i;
    for (i = 0; i < ctx->nrules; ++i)
        ctx->rules[i]->init_color_pairs(ctx->rules[i], color_pairs);
    return;
}

static void
_crulist_destroy_rules (crulist_t *self)
{
    crulist_ctx_t *ctx = self->ctx;
    int i;
    for (i = 0; i < ctx->nrules; ++i)
        ctx->rules[i]->destroy(ctx->rules[i]);
    ctx->nrules = 0;
    free(ctx->rules);
    ctx->rules = NULL;
    return;
}

static void
_crulist_destroy (crulist_t *self)
{
    self->destroy_rules(self);

    crulist_ctx_t *ctx = self->ctx;
    free(ctx);
    free(self);
    return;
}

crulist_t *
crulist_new (void)
{
    crulist_ctx_t *ctx = calloc(1, sizeof(*ctx));

    crulist_t *self = calloc(1, sizeof(*self));
    self->ctx = ctx;

    self->add         = _crulist_add;
    self->nrules      = _crulist_nrules;
    self->colorize    = _crulist_colorize;
    self->init_colors = _crulist_init_colors;
    self->destroy_rules  = _crulist_destroy_rules;
    self->destroy     = _crulist_destroy;
    
    return self;
}
